<!DOCTYPE html>
<html>
  <head>
    <title>
      Test Custom Oscillator at Very Low Frequency
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      // Create a custom oscillator and verify that the parts of a periodic wave
      // that should be ignored really are ignored.

      let sampleRate = 48000;

      // The desired frequency of the oscillator.  The value to be used depends
      // on the implementation of the PeriodicWave and should be less than then
      // lowest fundamental frequency. The lowest frequency is the Nyquist
      // frequency divided by the max number of coefficients used for the FFT.
      // In the current implementation, the max number of coefficients is 2048
      // (for a sample rate of 48 kHz) so the lowest frequency is 24000/2048 =
      // 11.78 Hz.
      let desiredFrequencyHz = 1;

      // Minimum allowed SNR between the actual oscillator and the expected
      // result.  Experimentally determined.
      let snrThreshold = 130;

      let context;
      let osc;
      let actual;

      let audit = Audit.createTaskRunner();

      // Compute the SNR between the actual result and expected cosine wave
      function checkCosineResult(should, result, freq, sampleRate) {
        let signal = 0;
        let noise = 0;
        let omega = 2 * Math.PI * freq / sampleRate;

        actual = result.getChannelData(0);

        for (let k = 0; k < actual.length; ++k) {
          let x = Math.cos(omega * k);
          let diff = x - actual[k];
          signal += x * x;
          noise += diff * diff;
        }

        let snr = 10 * Math.log10(signal / noise);

        should(snr, 'SNR of ' + desiredFrequencyHz + ' Hz sine wave')
            .beGreaterThanOrEqualTo(snrThreshold);
      }

      audit.define('low-freq-oscillator', (task, should) => {
        context = new OfflineAudioContext(1, sampleRate, sampleRate);
        osc = context.createOscillator();

        // Create the custom oscillator.  For simplicity of testing, we use just
        // a cosine wave, but the initial elements of the real and imaginary
        // parts are explicitly set to non-zero to test that they are ignored.
        let r = new Float32Array(2);
        let i = new Float32Array(2);
        r[0] = 1;  // DC component to be ignored
        r[1] = 1;  // Fundamental
        i[0] = 1;  // Sine term that doesn't actually exist in a Fourier series
        i[1] = 0;
        let wave = context.createPeriodicWave(r, i);

        osc.setPeriodicWave(wave);
        osc.frequency.value = desiredFrequencyHz;
        osc.connect(context.destination);
        osc.start();
        context.startRendering()
            .then(function(buffer) {
              checkCosineResult(should, buffer, desiredFrequencyHz, sampleRate);
            })
            .then(() => task.done());
      });

      audit.run();
    </script>
  </body>
</html>
